<template>
  <div
    class="cl-select"
    :class="{ 'is-diabled': disabled }"
    @click="toggelDropdown"
    @mouseenter="states.mouseHover = true"
    @mouseleave="states.mouseHover = false">
    <Tooltip
      placement="bottom-start"
      @click-outside="controlDropdown(false)"
      manual
      ref="tooltipRef"
      :popper-options="popperOptions">
      <Input
        type="text"
        v-model="states.inputValue"
        :disabled="disabled"
        :placeholder="filteredPlaceholder"
        ref="inputRef"
        :readonly="!filterable || !isDropdownShow"
        @input="debounceFilter"
        @keydown="handleKeydown">
        <template #suffix>
          <Icon
            icon="circle-xmark"
            v-if="showClearIcon"
            @mousedown.prevent="NOOP"
            @click.stop="onClear"></Icon>
          <Icon
            v-else
            icon="angle-down"
            class="header-angle"
            :class="{ 'is-active': isDropdownShow }"></Icon>
        </template>
      </Input>
      <template #content>
        <ul class="cl-select__menu">
          <div class="cl-select__loading" v-if="states.loading">
            <Icon icon="spinner" spin />
          </div>
          <div
            class="cl-select__nodata"
            v-else-if="filterable && filteredOptions.length === 0">
            no matching data
          </div>
          <template
            v-for="(item, index) in filteredOptions"
            :key="index"
            v-else>
            <li
              class="cl-select__menu-item"
              :class="{
                'is-disabled': item.disabled,
                'is-selected': states.selectOption?.value === item.value,
                'is-highlighted': states.highlightIndex === index
              }"
              :id="`select-item-${item.value}`"
              @click.stop="itemSelect(item)">
              <RenderVNode
                :vNode="
                  renderLabel ? renderLabel(item) : item.label
                "></RenderVNode>
            </li>
          </template>
        </ul>
      </template>
    </Tooltip>
  </div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import Tooltip from '../Tooltip/Tooltip.vue'
import Input from '../Input/Input.vue'
import Icon from '../Icon/Icon.vue'
import RenderVNode from '../Common/RenderVNode'
import { isFunction, debounce } from 'lodash-es'
import type {
  SelectEmits,
  SelectProps,
  SelectOption,
  SelectStates
} from './types'
import type { TooltipInstance } from '../Tooltip/types'
import type { InputInstance } from '../Input/types'
defineOptions({
  name: 'CLSelec'
})
const props = withDefaults(defineProps<SelectProps>(), {
  options: () => []
})
const emits = defineEmits<SelectEmits>()
const findOption = (value: string) => {
  const option = props.options.find(item => item.value === value)
  return option ? option : null
}
const tooltipRef = ref<TooltipInstance>()
const inputRef = ref<InputInstance>()
const isDropdownShow = ref(false)
let initailOption = findOption(props.modelValue)
watch(
  () => props.modelValue,
  val => {
    initailOption = findOption(val)
    states.inputValue = initailOption ? initailOption.label : ''
    states.selectOption = initailOption
  }
)
const filteredOptions = ref(props.options)
watch(
  () => props.options,
  val => {
    filteredOptions.value = val
  }
)
const generateFilterOptions = async (searchValue: string) => {
  if (!props.filterable) return
  if (props.filterable && isFunction(props.filterMethod)) {
    filteredOptions.value = props.filterMethod(searchValue)
  } else if (
    props.remote &&
    props.remoteMethod &&
    isFunction(props.remoteMethod)
  ) {
    states.loading = true
    try {
      filteredOptions.value = await props.remoteMethod(searchValue)
    } catch (e) {
      console.error(e)
      filteredOptions.value = []
    } finally {
      states.loading = false
    }
  } else {
    filteredOptions.value = props.options.filter(i =>
      i.label.includes(searchValue)
    )
  }
}
const timeout = computed(() => (props.remote ? 300 : 0))
const onFilter = () => {
  generateFilterOptions(states.inputValue)
}
const debounceFilter = debounce(() => {
  onFilter()
}, timeout.value)
const states = reactive<SelectStates>({
  inputValue: initailOption ? initailOption.label : '',
  selectOption: initailOption,
  mouseHover: false,
  loading: false,
  highlightIndex: -1
})
const popperOptions: any = {
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 9]
      }
    },
    {
      name: 'sameWidth',
      enabled: true,
      fn: ({ state }: { state: any }) => {
        state.styles.popper.width = `${state.rects.reference.width}px`
      },
      phase: 'beforeWrite',
      requires: ['computeStyles']
    }
  ]
}
const handleKeydown = (e: KeyboardEvent) => {
  console.log(e)
  switch (e.key) {
    case 'Enter':
      if (!isDropdownShow.value) {
        controlDropdown(true)
      } else {
        if (
          states.highlightIndex > -1 &&
          filteredOptions.value[states.highlightIndex]
        ) {
          itemSelect(filteredOptions.value[states.highlightIndex])
        } else {
          controlDropdown(false)
        }
      }
      break
    case 'Escape':
      if (isDropdownShow.value) {
        controlDropdown(false)
      }
      break
    case 'ArrowUp':
      e.preventDefault()
      if (filteredOptions.value.length > 0) {
        if (states.highlightIndex === -1 || states.highlightIndex === 0) {
          states.highlightIndex = filteredOptions.value.length - 1
        } else {
          states.highlightIndex--
        }
      }
      break
    case 'ArrowDown':
      e.preventDefault()
      if (filteredOptions.value.length > 0) {
        if (
          states.highlightIndex === -1 ||
          states.highlightIndex === filteredOptions.value.length - 1
        ) {
          states.highlightIndex = 0
        } else {
          states.highlightIndex++
        }
      }
      break
    default:
      break
  }
}
const showClearIcon = computed(() => {
  return (
    props.clearable &&
    states.mouseHover &&
    states.selectOption &&
    states.inputValue.trim() !== ''
  )
})
const filteredPlaceholder = computed(() => {
  return props.filterable && states.selectOption && isDropdownShow.value
    ? states.selectOption.label
    : props.placeholder
})
const onClear = () => {
  states.selectOption = null
  states.inputValue = ''
  emits('clear')
  emits('change', '')
  emits('update:modelValue', '')
}
const NOOP = () => {}
const controlDropdown = (show: boolean) => {
  if (show) {
    if (props.filterable && states.selectOption) {
      states.inputValue = ''
    }
    if (props.filterable) {
      generateFilterOptions(states.inputValue)
    }
    tooltipRef.value?.show()
  } else {
    if (props.filterable) {
      states.inputValue = states.selectOption ? states.selectOption.label : ''
    }
    tooltipRef.value?.hide()
    states.highlightIndex = -1
  }
  isDropdownShow.value = show
  emits('visible-change', show)
}
const toggelDropdown = () => {
  if (props.disabled) return
  if (isDropdownShow.value) {
    controlDropdown(false)
  } else {
    controlDropdown(true)
  }
}
const itemSelect = (e: SelectOption) => {
  if (e.disabled) return
  states.inputValue = e.label
  states.selectOption = e
  emits('change', e.value)
  emits('update:modelValue', e.value)
  inputRef.value?.ref.focus()
}
</script>
